/***************************************************************************
*   Copyright (C) 2010-2011 by swkyer <swkyer@gmail.com>                  *
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
*   This program is distributed in the hope that it will be useful,       *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
*   GNU General Public License for more details.                          *
*                                                                         *
*   You should have received a copy of the GNU General Public License     *
*   along with this program; if not, write to the                         *
*   Free Software Foundation, Inc.,                                       *
*   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <objbase.h>
#include <initguid.h>
#include <Setupapi.h>
#include "JlinkUSB.h"

#pragma comment (lib, "setupapi.lib")

#define PIPE_NUM_IN						0
#define PIPE_NUM_OUT					1

DEFINE_GUID(GUID_CLASS_JLINK, 0x54654e76, 0xdcf7, 0x4a7f, 0x87, 0x8a, 0x4e, 0x8f, 0xca, 0x0a, 0xcc, 0x9a);


static BOOL get_device_path(int device, char *devicepath)
{
	BOOL bResValue = FALSE;
	ULONG requiredLength;
	ULONG predictedLength;
	HDEVINFO hardwareDeviceInfo;
	SP_DEVICE_INTERFACE_DATA deviceInfoData;
	SP_DEVINFO_DATA device_info_data;
	PSP_DEVICE_INTERFACE_DETAIL_DATA functionClassDeviceData = NULL;
	LPGUID pGuid = (LPGUID)&GUID_CLASS_JLINK;

	hardwareDeviceInfo = SetupDiGetClassDevs(pGuid, NULL, NULL, (DIGCF_PRESENT|DIGCF_DEVICEINTERFACE));

	deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);	
	if (SetupDiEnumDeviceInterfaces(hardwareDeviceInfo, 0, pGuid, device, &deviceInfoData))
	{
		SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, &deviceInfoData, NULL, 0, &requiredLength, NULL);

		functionClassDeviceData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredLength);
		if (functionClassDeviceData == NULL)
			return FALSE;
		functionClassDeviceData->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);

		device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
		predictedLength = requiredLength;
		if (!SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, &deviceInfoData, functionClassDeviceData,
											predictedLength, &requiredLength, &device_info_data))
		{
			free(functionClassDeviceData);	
			goto USB_CreateHandle_Return;
		}

		strcpy(devicepath, functionClassDeviceData->DevicePath);
		bResValue = TRUE;

		free(functionClassDeviceData);
	}

USB_CreateHandle_Return :
	SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
	return bResValue;
}


JLINKUSB_API jlink_usb_t *jlinkusb_open(int device)
{
	char device_path[MAX_PATH];
	char device_pipe[MAX_PATH];
	char pipe_name[32];
	jlink_usb_t *handle;

	handle = (jlink_usb_t *)malloc(sizeof(jlink_usb_t));
	if (handle == NULL)
		return NULL;
	memset(handle, 0, sizeof(jlink_usb_t));
	handle->m_nTimeout = JLINK_COMM_DEFAULT_TIMEOUT;

	handle->m_hReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (handle->m_hReadEvent == INVALID_HANDLE_VALUE)
	{
		free(handle);
		return NULL;
	}

	handle->m_Mutex = CreateMutex(NULL, FALSE, NULL);
	if (handle->m_Mutex == INVALID_HANDLE_VALUE)
	{
		CloseHandle(handle->m_hReadEvent);
		free(handle);
		return NULL;
	}

	memset(device_path, 0, MAX_PATH);
	memset(device_pipe, 0, MAX_PATH);
	memset(pipe_name, 0, 32);
	if (get_device_path(device, device_path) == FALSE)
	{
		CloseHandle(handle->m_Mutex);
		CloseHandle(handle->m_hReadEvent);
		free(handle);
		return NULL;
	}

	strcpy(device_pipe, device_path);
	sprintf(pipe_name, "\\pipe0%d", PIPE_NUM_IN);	
	strcat(device_pipe, pipe_name);
	handle->m_PipeIn = CreateFile(device_pipe,
						GENERIC_READ | GENERIC_WRITE,
						FILE_SHARE_READ | FILE_SHARE_WRITE,
						NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if (handle->m_PipeIn == INVALID_HANDLE_VALUE)
	{
		CloseHandle(handle->m_Mutex);
		CloseHandle(handle->m_hReadEvent);
		free(handle);
		return NULL;
	}

	strcpy(device_pipe, device_path);
	sprintf(pipe_name, "\\pipe0%d", PIPE_NUM_OUT);
	strcat(device_pipe, pipe_name);
	handle->m_PipeOut = CreateFile(device_pipe,
						GENERIC_READ | GENERIC_WRITE,
						FILE_SHARE_READ | FILE_SHARE_WRITE,
						NULL, OPEN_EXISTING, 0/*FILE_FLAG_OVERLAPPED*/, NULL);
	if (handle->m_PipeOut == INVALID_HANDLE_VALUE)
	{
		CloseHandle(handle->m_PipeIn);
		CloseHandle(handle->m_Mutex);
		CloseHandle(handle->m_hReadEvent);
		free(handle);
		return NULL;
	}

	return handle;
}

JLINKUSB_API int jlinkusb_close(jlink_usb_t *usbdev)
{
	if (usbdev == NULL)
		return -1;

	CloseHandle(usbdev->m_PipeIn);
	CloseHandle(usbdev->m_PipeOut);
	CloseHandle(usbdev->m_Mutex);
	CloseHandle(usbdev->m_hReadEvent);

	free(usbdev);
	return 0;
}

JLINKUSB_API int jlinkusb_lock(jlink_usb_t *usbdev)
{
	if (usbdev == NULL)
		return -1;

	WaitForSingleObject(usbdev->m_Mutex, INFINITE);
	return 0;
}

JLINKUSB_API int jlinkusb_unlock(jlink_usb_t *usbdev)
{
	if (usbdev == NULL)
		return -1;

	ReleaseMutex(usbdev->m_Mutex);
	return 0;
}

JLINKUSB_API int jlinkusb_read(jlink_usb_t *usbdev, uint8_t *buffp, int count)
{
	DWORD dwBytes = 0;
	BOOL bReadStatus;
	DWORD dwError, dwr;
	OVERLAPPED overlapped;

	memset(&overlapped, 0, sizeof(OVERLAPPED));
	overlapped.hEvent = usbdev->m_hReadEvent;
	ResetEvent(overlapped.hEvent);

	bReadStatus = ReadFile(usbdev->m_PipeIn, buffp, count, &dwBytes, &overlapped);
	if (!bReadStatus) 
	{ 
		dwError = GetLastError();
		switch (dwError) 
		{ 
		case ERROR_HANDLE_EOF: 
			{
				// we're reached the end of the file 
				// during the call to ReadFile 
				// code to handle that 
				dwBytes = -1;
			}
		case ERROR_IO_PENDING: 
			{
				// asynchronous i/o is still in progress 				
				// do something else for a while 
				dwr = WaitForSingleObject(overlapped.hEvent, usbdev->m_nTimeout);
				if (dwr == WAIT_TIMEOUT)
				{
					// cancel io
					CancelIo(usbdev->m_PipeIn);
				}

				// check on the results of the asynchronous read 
				bReadStatus = GetOverlappedResult(usbdev->m_PipeIn, &overlapped, &dwBytes, FALSE);
				// if there was a problem ... 
				if (!bReadStatus) 
				{ 
					dwError = GetLastError();
					switch (dwError) 
					{ 
					case ERROR_HANDLE_EOF: 
						{ 
							// we're reached the end of the file 
							// during asynchronous operation 
						} 
						// deal with other error cases 
						dwBytes = -1;
					} 
				} 
				break;
			}
		case ERROR_BAD_COMMAND:
			dwBytes = -2;
			break;
		default:
			dwBytes = -1 * dwError;
			break;
		}
	}

	return dwBytes;
}

JLINKUSB_API int jlinkusb_write(jlink_usb_t *usbdev, uint8_t *buffp, int count)
{
	BOOL bReadStatus;
	DWORD dwBytesWritten, dwError;

	if (usbdev == NULL || buffp == NULL)
		return -1;

	bReadStatus = WriteFile(usbdev->m_PipeOut, buffp, count, &dwBytesWritten, 0);
	if (!bReadStatus)
	{
		dwError = GetLastError();
		return (-1 * dwError);
	}

	return dwBytesWritten;
}

JLINKUSB_API int jlinkusb_access(jlink_usb_t *usbdev, uint8_t *buffw, int wcount, uint8_t *buffr, int rcount)
{
	DWORD dwBytes = 0;
	BOOL bReadStatus, bWriteStatus;
	DWORD dwBytesWritten, dwError, dwr;
	OVERLAPPED overlapped;

	if (usbdev == NULL)
		return -1000;
	if ((buffw == NULL || wcount < 0) || (buffr == NULL || rcount < 0))
		return -1001;

	memset(&overlapped, 0, sizeof(OVERLAPPED));
	overlapped.hEvent = usbdev->m_hReadEvent;
	ResetEvent(overlapped.hEvent);
	bReadStatus = ReadFile(usbdev->m_PipeIn, buffr, rcount, &dwBytes, &overlapped);
	dwError = GetLastError();
	if (bReadStatus && dwError != ERROR_IO_PENDING)
	{
		return dwBytes;
	}

	bWriteStatus = WriteFile(usbdev->m_PipeOut, buffw, wcount, &dwBytesWritten, 0);
	if (!bWriteStatus || dwBytesWritten != wcount)
	{
		// TODO, error handling
	}

	if (!bReadStatus) 
	{ 
		dwError = GetLastError();
		switch (dwError) 
		{ 
		case ERROR_HANDLE_EOF: 
			{ 
				// we're reached the end of the file 
				// during the call to ReadFile 
				// code to handle that 
				dwBytes = -1;
			}
		case ERROR_IO_PENDING: 
			{ 
				// asynchronous i/o is still in progress 				
				// do something else for a while 
				dwr = WaitForSingleObject(overlapped.hEvent, usbdev->m_nTimeout);
				if (dwr == WAIT_TIMEOUT)
				{
					// cancel io
					CancelIo(usbdev->m_PipeIn);
				}

				// check on the results of the asynchronous read 
				bReadStatus = GetOverlappedResult(usbdev->m_PipeIn, &overlapped, &dwBytes, FALSE);
				if (!bReadStatus) 
				{ 
					dwError = GetLastError();
					switch (dwError) 
					{ 
					case ERROR_HANDLE_EOF: 
						{ 
							// we're reached the end of the file 
							// during asynchronous operation 
						} 
						// deal with other error cases 
						dwBytes = -1;
					} 
				} 
				break;
			}
		case ERROR_BAD_COMMAND:
			dwBytes = -2;
			break;
		default:
			dwBytes = -1 * dwError;
			break;
		}
	}

	return dwBytes;
}

JLINKUSB_API int jlinkusb_get_timeout(jlink_usb_t *usbdev)
{
	int timeout;

	if (usbdev == NULL)
		return -1;

	WaitForSingleObject(usbdev->m_Mutex, INFINITE);
	timeout = usbdev->m_nTimeout;
	ReleaseMutex(usbdev->m_Mutex);

	return timeout;
}

JLINKUSB_API int jlinkusb_set_timeout(jlink_usb_t *usbdev, int tc)
{
	if (usbdev == NULL)
		return -1;

	WaitForSingleObject(usbdev->m_Mutex, INFINITE);
	usbdev->m_nTimeout = tc;
	ReleaseMutex(usbdev->m_Mutex);

	return tc;
}
